package com.huashun.ship.run.shiplock.service;

import com.huashun.ship.run.shiplock.dto.ShipLockDetailDTO;
import com.huashun.ship.run.shiplock.dto.ShipLockOverviewDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;

/**
 * 船闸相关功能业务接口实现类
 * @author yzh
 * @version 0.0.1
 * @since 20200510
 */
@Slf4j
@Component("shipLockService")
public class ShipLockServiceImpl implements ShipLockService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    private String dataHost = "http://www.xjsy56.com/portal/";  //抓取数据的host，用于拼接生成抓取数据的url

    /**
     * 查询获取所有船闸过闸情况总览数据
     *
     * @return
     */
    @Override
    public List<ShipLockOverviewDTO> getAllShipLockOverviewDTOs() {
        //首先尝试从redis中获取缓存的过闸总览数据
        log.debug("尝试从redis数据库中获取过闸总览数据");
        List<ShipLockOverviewDTO> overviewDTOS = (List<ShipLockOverviewDTO>)redisTemplate.opsForValue().get("ShipLockOverviewDTOs");

        if (CollectionUtils.isEmpty(overviewDTOS)){
            log.debug("数据库中不存在所需要的数据，自动调用刷新接口重新抓取数据");
            //如果redis中没有过闸总览数据，那么调用刷新接口从西江水运物流网重新抓取数据保存到redis后再查询一次。
            try {
                this.reloadShipLockOverviews();
                overviewDTOS = (List<ShipLockOverviewDTO>)redisTemplate.opsForValue().get("ShipLockOverviewDTOs");
            } catch (IOException e){
                log.error("抓取数据时遇到IO错误，数据抓取失败，平台自动空列表。");
                return new ArrayList<ShipLockOverviewDTO>();
            }
        }

        return overviewDTOS;
    }

    /**
     * 根据船闸代码和航行方向查询获取船舶过闸的详细信息列表
     *
     * @param shipLockCode
     * @param direction
     * @return
     */
    @Override
    public List<ShipLockDetailDTO> getShipLockDetailDTOs(String shipLockCode, String direction) {
        //首先尝试从redis中获取缓存的过闸总览数据
        log.debug("尝试从redis数据库中获取代码为" + shipLockCode + "，方向为" + "的船闸的过闸详细信息列表");
        List<ShipLockDetailDTO> detailDTOs = (List<ShipLockDetailDTO>)redisTemplate.opsForValue().get("ShipLockDetailDTOs:" + shipLockCode + "-" + direction);

        if (CollectionUtils.isEmpty(detailDTOs)){
            log.debug("数据库中不存在所需要的数据，自动调用刷新接口重新抓取数据");
            //如果redis中没有过闸总览数据，那么调用刷新接口从西江水运物流网重新抓取数据保存到redis后再查询一次。
            try {
                this.reloadShipLockDetails(shipLockCode, direction);
                detailDTOs = (List<ShipLockDetailDTO>)redisTemplate.opsForValue().get("ShipLockDetailDTOs:" + shipLockCode + "-" + direction);
            } catch (IOException e){
                log.error("抓取数据时遇到IO错误，数据抓取失败，平台自动空列表。");
                return new ArrayList<ShipLockDetailDTO>();
            }
        }

        return detailDTOs;
    }

    /**
     * 刷新redis中的船闸过闸情况总览数据
     */
    @Override
    public void reloadShipLockOverviews() throws IOException {
        String requestUri = dataHost + "showShiplockSchedule";
        List<ShipLockOverviewDTO> overviewDTOs = new ArrayList<>();

        Document document = Jsoup.connect(requestUri).get();    //抓取页面代码并转换为Document对象
        List<Element> overViewDatas = document.body().select("#dataTable").select("tbody").select("tr");    //取总览部分的代码

        //遍历总览代码，解析出枢纽船闸等供页面展示的数据
        for (int i = 0; i < overViewDatas.size(); i++){
            List<Element> rowDatas = overViewDatas.get(i).select("td"); //取得当前要解析的tr代码
            ShipLockOverviewDTO details = new ShipLockOverviewDTO();

            details.setShipLockName(rowDatas.get(1).select("a").get(0).text()); //枢纽船闸名

            details.setWaittingDown(rowDatas.get(2).text());    //下行排队数量
            details.setSchedulingDown(rowDatas.get(3).text());  //下行调度中数量
            details.setYesterdayDown(rowDatas.get(4).text());  //昨日下行过闸数
            details.setTodayDown(rowDatas.get(5).text());  //今日下行过闸数

            details.setWaittingUp(rowDatas.get(6).text());    //上行排队数量
            details.setSchedulingUp(rowDatas.get(7).text());  //上行调度中数量
            details.setYesterdayUp(rowDatas.get(8).text());  //昨日上行过闸数
            details.setTodayUp(rowDatas.get(9).text());  //今日上行过闸数

            details.setWaterDepthOfDownstream(rowDatas.get(10).text());  //下游水位
            details.setWaterDepthOfUpstream(rowDatas.get(11).text());  //上游水位

            //船闸代码
            String href = rowDatas.get(1).select("a").get(0).attr("href").replace("/portal/showShiplockDyn?sign=", "");
            String shipLockCode = "";
            if (href.indexOf("&") >= 0){
                shipLockCode = href.substring(0, href.indexOf("&"));
            }
            else {
                shipLockCode = href;
            }
            details.setShipLockCode(shipLockCode);

            overviewDTOs.add(details);
        }

        //抓取合计数据
        List<Element> totalRowDatas = document.body().select("#dataTable").select("tfoot").select("tr").get(0).select("td");  //合计行的代码
        ShipLockOverviewDTO summation = new ShipLockOverviewDTO();
        summation.setWaittingDown(totalRowDatas.get(2).text());    //下行排队数量合计
        summation.setSchedulingDown(totalRowDatas.get(3).text());  //下行调度中数量合计
        summation.setYesterdayDown(totalRowDatas.get(4).text());  //昨日下行过闸数合计
        summation.setTodayDown(totalRowDatas.get(5).text());  //今日下行过闸数合计

        summation.setWaittingUp(totalRowDatas.get(6).text());    //上行排队数量合计
        summation.setSchedulingUp(totalRowDatas.get(7).text());  //上行调度中数量合计
        summation.setYesterdayUp(totalRowDatas.get(8).text());  //昨日上行过闸数合计
        summation.setTodayUp(totalRowDatas.get(9).text());  //今日上行过闸数合计

        summation.setWaterDepthOfDownstream(totalRowDatas.get(10).text());  //下游水位（不适用，读取为“—”）
        summation.setWaterDepthOfUpstream(totalRowDatas.get(11).text());  //上游水位（不适用，读取为“—”）

        overviewDTOs.add(summation);

        //将抓取到的数据保存到redis中
        if (overviewDTOs.size() > 0){
            this.redisTemplate.opsForValue().set("ShipLockOverviewDTOs", overviewDTOs);
        }
    }

    /**
     * 根据船闸代码和航行方向刷新对应船闸对应航行方向的过闸详细信息列表
     *
     * @param shipLockCode
     * @param direction
     */
    @Override
    public void reloadShipLockDetails(String shipLockCode, String direction) throws IOException {
        List<ShipLockDetailDTO> datas = new ArrayList<>();
        String requestUri = dataHost + "showShiplockDyn?sign=" + shipLockCode + "&curs_cd=" + URLEncoder.encode(direction.equals("up") ? "上行" : "下行");

        //递归抓取船舶过闸明细数据
        datas = this.loadAllShiplockDetailsWithRecursion(datas, requestUri, "reg", 1, 100);  //锚地船舶数据
        datas = this.loadAllShiplockDetailsWithRecursion(datas, requestUri, "side", 1, 100); //靠船墩船舶数据
        datas = this.loadAllShiplockDetailsWithRecursion(datas, requestUri, "room", 1, 100); //闸室船舶数据

        Collections.sort(datas, new Comparator<ShipLockDetailDTO>() {
            @Override
            public int compare(ShipLockDetailDTO o1, ShipLockDetailDTO o2) {
                int a = Integer.parseInt(o1.getQueueNo());
                int b = Integer.parseInt(o2.getQueueNo());
                if (a > b) {
                    return 1;
                } else if(a == b) {
                    return 0;
                } else {
                    return -1;
                }
            }
        });

        if (datas.size() > 0){
            this.redisTemplate.opsForValue().set("ShipLockDetailDTOs:" + shipLockCode + "-" + direction, datas);
        }
    }

    /**
     * 递归抓取船闸过闸明细数据
     * @param datas
     * @param baseUrl
     * @param type
     * @param pageNum
     * @param pageSize
     * @return
     */
    private List<ShipLockDetailDTO> loadAllShiplockDetailsWithRecursion(List<ShipLockDetailDTO> datas, String baseUrl, String type, int pageNum, int pageSize) {
        String requestUrl = baseUrl + "&type=" + type + "&pageNumber=" + pageNum + "&pageSize=" + pageSize;
        log.debug("当前请求数据的url为：" + requestUrl);

        //生成用于获取html的httpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        HttpGet request = new HttpGet(requestUrl);
        //将request的User-Agent伪装成Chrome，避免被识别为爬虫
        request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36");
        try {
            //抓取远程的html代码
            String htmlSrc = "";
            response = httpClient.execute(request);
            if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //只有返回状态码为200（请求成功）时才抓取html
                HttpEntity httpEntity = response.getEntity();
                htmlSrc = EntityUtils.toString(httpEntity, "utf-8");
            }

            if (log.isDebugEnabled()){
                log.debug("抓取到远程的html为：");
                log.debug(htmlSrc);
            }

            if (htmlSrc.length() > 0){
                Document document = Jsoup.parse(htmlSrc);
                List<Element> detailDatas = new ArrayList<>();

                if (type.equals("reg")){    //锚地船舶
                    detailDatas = document.body().select("#paidui01").select("tbody").select("tr");   //抓取船闸动态数据区域的代码
                }
                else if (type.equals("side")){  //靠船墩船舶
                    detailDatas = document.body().select("#kao01").select("tbody").select("tr");   //抓取船闸动态数据区域的代码
                }
                else if (type.equals("room")){  //闸室船舶
                    detailDatas = document.body().select("#zha01").select("tbody").select("tr");   //抓取船闸动态数据区域的代码
                }

                //取当前年份，用于替换掉报到时间中的年，保持数据格式一致
                String year = String.valueOf(Calendar.getInstance().get(Calendar.YEAR)) + "-";

                if (detailDatas.size() > 0){
                    //有抓取到数据了，那么遍历添加数据
                    if (type.equals("reg")){    //锚地船舶
                        for (int i = 0; i < detailDatas.size(); i++){
                            List<Element> dataCells = detailDatas.get(i).select("td");   //当前要解析的数据行
                            if (StringUtils.hasText(dataCells.get(0).text())){
                                //当前行有排队号，那么解析当前行的数据
                                ShipLockDetailDTO detailData = new ShipLockDetailDTO();
                                detailData.setQueueNo(dataCells.get(0).text());    //排队号
                                detailData.setShipName(dataCells.get(1).text());    //船舶名称
                                detailData.setDraughtDepth(dataCells.get(2).text());    //吃水深度
                                detailData.setSignTime(dataCells.get(3).text().replace(year, ""));    //报到时间
                                detailData.setStatus(dataCells.get(4).text());  //状态
                                detailData.setTotalWeight(dataCells.get(5).text()); //总吨
                                detailData.setNuclearLoadWeight(dataCells.get(6).text());   //核载
                                detailData.setActualLoadWeight(dataCells.get(7).text());    //实载
                                detailData.setShipLength(dataCells.get(8).text());  //长度
                                detailData.setShipWidth(dataCells.get(9).text());   //宽度
                                detailData.setShipHeight(dataCells.get(10).text()); //高度
                                detailData.setGoods(dataCells.get(11).text());  //货物

                                //锚地没有闸室和位置属性，固定设置为空
                                detailData.setShipLockName("");  //闸室
                                detailData.setLocation("");  //位置

                                datas.add(detailData);
                            } else {    //如果当前行没有排队号，说明已经抓取了所有数据了，那么返回递归结果
                                return datas;
                            }
                        }
                    }
                    else if (type.equals("side")){  //靠船墩船舶
                        for (int i = 0; i < detailDatas.size(); i++){
                            List<Element> dataCells = detailDatas.get(i).select("td");   //当前要解析的数据行
                            if (StringUtils.hasText(dataCells.get(0).text())){
                                //当前行有排队号，那么解析当前行的数据
                                ShipLockDetailDTO detailData = new ShipLockDetailDTO();
                                detailData.setQueueNo(dataCells.get(0).text());    //排队号
                                detailData.setShipName(dataCells.get(1).text());    //船舶名称
                                detailData.setSignTime(dataCells.get(2).text().replace(year, ""));    //报到时间
                                detailData.setLocation(dataCells.get(3).text());    //位置
                                detailData.setDraughtDepth(dataCells.get(4).text());  //吃水深度
                                detailData.setTotalWeight(dataCells.get(5).text()); //总吨
                                detailData.setNuclearLoadWeight(dataCells.get(6).text());   //核载
                                detailData.setActualLoadWeight(dataCells.get(7).text());    //实载
                                detailData.setShipLength(dataCells.get(8).text());  //长度
                                detailData.setShipWidth(dataCells.get(9).text());   //宽度
                                detailData.setShipHeight(dataCells.get(10).text()); //高度
                                detailData.setGoods(dataCells.get(11).text());  //货物

                                //靠船墩船舶没有闸室和状态属性，固定设置为空
                                detailData.setShipLockName("");  //闸室
                                detailData.setStatus("");  //状态

                                datas.add(detailData);
                            } else {    //如果当前行没有排队号，说明已经抓取了所有数据了，那么返回递归结果
                                return datas;
                            }
                        }
                    }
                    else if (type.equals("room")){  //闸室船舶
                        for (int i = 0; i < detailDatas.size(); i++){
                            List<Element> dataCells = detailDatas.get(i).select("td");   //当前要解析的数据行
                            if (StringUtils.hasText(dataCells.get(0).text())){
                                //当前行有排队号，那么解析当前行的数据
                                ShipLockDetailDTO detailData = new ShipLockDetailDTO();
                                detailData.setQueueNo(dataCells.get(0).text());    //排除号
                                detailData.setShipName(dataCells.get(1).text());    //船舶名称
                                detailData.setSignTime(dataCells.get(2).text().replace(year, ""));    //报到时间
                                detailData.setDraughtDepth(dataCells.get(3).text());  //吃水深度
                                detailData.setTotalWeight(dataCells.get(4).text()); //总吨
                                detailData.setNuclearLoadWeight(dataCells.get(5).text());   //核载
                                detailData.setActualLoadWeight(dataCells.get(6).text());    //实载
                                detailData.setGoods(dataCells.get(7).text());  //货物
                                detailData.setShipLength(dataCells.get(8).text());  //长度
                                detailData.setShipWidth(dataCells.get(9).text());   //宽度
                                detailData.setShipHeight(dataCells.get(10).text()); //高度
                                detailData.setShipLockName(dataCells.get(11).text());    //闸室

                                //闸室船舶没有位置和状态属性，固定设置为空
                                detailData.setLocation("");  //位置
                                detailData.setStatus("");  //状态

                                datas.add(detailData);
                            } else {    //如果当前行没有排队号，说明已经抓取了所有数据了，那么返回递归结果
                                return datas;
                            }
                        }
                    }

                    //循环完当前页面的数据后，继续抓取下一页的数据
                    int nextPageNum = pageNum + 1;

                    //两次抓取数据之间留10秒的间隔，防止因频繁抓取数据触发远程网站的防爬机制。add by yzh since 20200117
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    loadAllShiplockDetailsWithRecursion(datas, baseUrl, type, nextPageNum, pageSize);
                }
                else {  //如果没有船闸动态数据，那么返回当前的递归结果
                    return datas;
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpClient);
        }

        return datas;
    }
}
